// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections;
using System.Collections.Generic;

namespace SnapObjects.Data
{
    internal abstract class DynamicModelBase : IDynamicModelBase
    {
        protected readonly IDictionary<Type, KeyValuePair<TypeCode, BitArray>> _typeInfo;
        protected DynamicModelBase(int count)
        {
            this.PropertyCount = count;

            _typeInfo = new Dictionary<Type, KeyValuePair<TypeCode, BitArray>>();

            this.InitialTypeInfo();
        }

        protected DynamicModelBase(object[] values, int count) : this(count)
        {
            _typeInfo = new Dictionary<Type, KeyValuePair<TypeCode, BitArray>>();

            this.InitialTypeInfo();

            this.Setter(values);
        }

        public int PropertyCount { get; }

        public T GetValue<T>(int index)
        {
            var value = this.GetValue(index);

            return ValueConvert.Convert<T>(value);
        }

        public object GetValue(int index)
        {
            return this[index];
        }

        public void SetValue(int index, object value)
        {
            this.Setter(index, value);
        }

        public void SetValues(object[] values)
        {
            this.Setter(values);
        }

        public object this[int index]
        {
            get => this.Getter(index);
            set => this.Setter(index, value);
        }

        protected abstract object Getter(int index);

        protected abstract void Setter(int index, object value);

        protected abstract void InitialTypeInfo();

        protected void SetTypeInfo(IList<Type> types)
        {
            foreach (var type in types)
            {
                var tempType = Nullable.GetUnderlyingType(type) ?? type;

                var typeCode = Type.GetTypeCode(tempType);

                if (!_typeInfo.ContainsKey(type))
                {
                    var bitArra = new BitArray(2);

                    bitArra[0] = this.CanCompatible(typeCode);
                    bitArra[1] = this.IsUnsignedType(typeCode);

                    _typeInfo.Add(type, new KeyValuePair<TypeCode, BitArray>(typeCode, bitArra));
                }
            }
        }

        protected TValue GetCompatibleValue<TValue>(object value)
        {
            if (value is TValue tv)
            {
                return tv;
            }

            if (value != null && _typeInfo.TryGetValue(typeof(TValue), out var v))
            {
                var canCompatible = v.Value[0];
                var valueTypeCode = v.Key;
                var isUnsignedType = v.Value[1];

                var tempType = value.GetType();

                var type = Nullable.GetUnderlyingType(tempType) ?? tempType;

                var typeCode = Type.GetTypeCode(type);

                if (typeCode > TypeCode.Char && typeCode < valueTypeCode)
                {
                    if (valueTypeCode == TypeCode.Decimal)
                    {
                        if (typeCode != TypeCode.Double && typeCode != TypeCode.Single)
                        {
                            value = Convert.ChangeType(value, valueTypeCode);
                        }
                    }
                    else if (!isUnsignedType || (isUnsignedType && this.IsUnsignedType(typeCode)))
                    {
                        value = Convert.ChangeType(value, valueTypeCode);
                    }
                }
            }

            return (TValue)value;
        }

        private bool CanCompatible(TypeCode typeCode)
        {
            switch (typeCode)
            {
                case TypeCode.Double:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                case TypeCode.Single:
                case TypeCode.Decimal:
                case TypeCode.Byte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsUnsignedType(TypeCode typeCode)
        {
            switch (typeCode)
            {
                case TypeCode.Byte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    return true;
            }

            return false;
        }

        private void Setter(object[] values)
        {
            var len = values.Length;

            if (len > this.PropertyCount)
            {
                len = this.PropertyCount;
            }

            for (var i = 0; i < len; i++)
            {
                this.Setter(i, values[i]);
            }
        }
    }
}
